﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace RailwayDAL
{
    public static class RailwayDBManager
    {
        private static void SaveEntity(RailwayCompanyEntities entity)
        {
            try
            {
                entity.SaveChanges();
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.ToString());
            }
        }

        #region Stations

        public static void CreateStation(Station station)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                entity.Stations.AddObject(station);

                SaveEntity(entity);
            }
        }

        public static void ModifyStation(int stationID, Station station)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                Station modifiedStation = GetStationByID(stationID);

                if (modifiedStation != null)
                {
                    entity.Attach(modifiedStation);
                    modifiedStation.StationName = station.StationName;
                    modifiedStation.OffsetX = station.OffsetX;
                    modifiedStation.OffsetY = station.OffsetY;

                    SaveEntity(entity);
                }
                else
                {
                    throw new ArgumentException(String.Format("Station not found if:{0}}", stationID));
                }
            }
        }

        public static Station GetStationByID(int stationID)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                Station station = entity.Stations.FirstOrDefault(s => s.StationID == stationID);

                return station;
            }
        }

        public static Station GetStationByName(string stationName)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                Station station = entity.Stations.FirstOrDefault(s => s.StationName == stationName);

                return station;
            }
        }

        public static void DeleteStation(int stationID)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                Station stationToDelete = GetStationByID(stationID);

                if (stationToDelete != null)
                {
                    entity.Attach(stationToDelete);
                    entity.Stations.DeleteObject(stationToDelete);
                    SaveEntity(entity);
                }
                else
                {
                    throw new ArgumentException(String.Format("Station not found if:{0}}", stationID));
                }
            }
        }

        #endregion

        #region TravelTimes

        public static void CreateTravelTime(TravelTime travelTime)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                entity.TravelTimes.AddObject(travelTime);

                SaveEntity(entity);
            }
        }

        public static void ModifyTravelTime(int travelTimeID, TravelTime travelTime)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                TravelTime modifiedTravelTime = GetTravelTimeByID(travelTimeID);

                if (modifiedTravelTime != null)
                {
                    entity.Attach(modifiedTravelTime);
                    modifiedTravelTime.Station1 = travelTime.Station1;
                    modifiedTravelTime.Station2 = travelTime.Station2;
                    modifiedTravelTime.Time = travelTime.Time;

                    SaveEntity(entity);
                }
                else
                {
                    throw new ArgumentException(String.Format("Travel Time not found if:{0}}", travelTimeID));
                }
            }
        }

        public static TravelTime GetTravelTimeByID(int travelTimeID)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                TravelTime travelTime = entity.TravelTimes.FirstOrDefault(t => t.TravelTimeID == travelTimeID);

                return travelTime;
            }
        }

        private static TravelTime GetTravelTimeByStations(int firstStation, int secondStation)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                TravelTime travelTime = entity.TravelTimes.FirstOrDefault(t => (t.Station1 == firstStation) && (t.Station2 == secondStation));
                if (travelTime==null)
                {
                    travelTime = entity.TravelTimes.FirstOrDefault(t => (t.Station1 == secondStation) && (t.Station2 == firstStation));
                }

                return travelTime;
            }
        }

        public static void DeleteTravelTime(int travelTimeID)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                TravelTime travelTimeToDelete = GetTravelTimeByID(travelTimeID);

                if (travelTimeToDelete != null)
                {
                    entity.Attach(travelTimeToDelete);
                    entity.TravelTimes.DeleteObject(travelTimeToDelete);
                    SaveEntity(entity);
                }
                else
                {
                    throw new ArgumentException(String.Format("Travel Time not found if:{0}}", travelTimeID));
                }
            }
        }

        #endregion

        #region Routes

        public static void CreateRoute(TimeSpan departureTime, List<Station> stations, string routeStations)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                Route route = new Route();
                route.DepartureTime = departureTime;
                route.RouteStationNames = routeStations;

                entity.Routes.AddObject(route);
                SaveEntity(entity);

                AddRoutesStations(stations, route.RouteID);
            }
        }

        private static void AddRoutesStations(List<Station> stations, int routeID)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                for (int i = 0; i < stations.Count; i++)
			{
                    var station = stations[i];
                    RoutesStation routeStation = new RoutesStation();
                    routeStation.RouteID = routeID;
                    routeStation.StationID = station.StationID;
                    routeStation.StationNumber = i;
                    entity.RoutesStations.AddObject(routeStation);
                }

                SaveEntity(entity);
            }
        }

        public static void ModifyRoute(int routeID, TimeSpan departureTime, List<Station> stations)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                Route modifiedRoute = GetRouteByID(routeID);

                if (modifiedRoute != null)
                {
                    entity.Attach(modifiedRoute);
                    modifiedRoute.DepartureTime = departureTime;
                    AddRoutesStations(stations, modifiedRoute.RouteID);

                    SaveEntity(entity);
                }
                else
                {
                    throw new ArgumentException(String.Format("Route not found if:{0}}", routeID));
                }
            }
        }

        public static Route GetRouteByID(int routeID)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                Route route = entity.Routes.FirstOrDefault(r => r.RouteID == routeID);

                return route;
            }
        }

        public static void DeleteRouteTime(int routeID)
        {
            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                Route travelTimeToDelete = GetRouteByID(routeID);

                if (travelTimeToDelete != null)
                {
                    entity.Attach(travelTimeToDelete);
                    entity.Routes.DeleteObject(travelTimeToDelete);
                    SaveEntity(entity);
                }
                else
                {
                    throw new ArgumentException(String.Format("Travel Time not found if:{0}}", routeID));
                }
            }
        }

        #endregion

        public static List<Route> GetTrainsPassingThroughStation(int stationID)
        {
            List<Route> trains = new List<Route>();

            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                var routesStations = entity.RoutesStations;
                foreach (RoutesStation item in routesStations)
                {
                    if (item.StationID==stationID)
	                {
                        Route route = GetRouteByID(item.RouteID);
                        trains.Add(route);
	                }
                }
            }

            return trains;
        }

        public static SortedDictionary<TimeSpan, Route> GetAllDirectTrains(int departureStationID, int arriveStationID)
        {
            SortedDictionary<TimeSpan, Route> directTrains = new SortedDictionary<TimeSpan, Route>();

            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                var routesStations = entity.RoutesStations;
                foreach (RoutesStation firstItem in routesStations)
                {
                    if (firstItem.StationID == departureStationID)
                    {
                        int stationNumber = firstItem.StationNumber;
                        int routeID = firstItem.RouteID;
                        foreach (RoutesStation secondItem in routesStations)
                        {
                            if (secondItem.StationID==arriveStationID && secondItem.RouteID==routeID
                                && secondItem.StationNumber==(stationNumber-1))
                            {
                                Route route = GetRouteByID(routeID);
                                TimeSpan departureTime = CalculateStationDepartureTime(departureStationID, route);
                                directTrains[departureTime] = route;
                            }
                        }
                    }
                }
            }

            return directTrains;
        }

        private static TimeSpan CalculateStationDepartureTime(int departureStationID, Route route)
        {
            TimeSpan departureTime = route.DepartureTime;

            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                var routesStations = entity.RoutesStations;
                foreach (RoutesStation item in routesStations)
                {
                    int previousStation = 0;
                    if (item.RouteID==route.RouteID)
                    {
                        if (item.StationID==departureStationID)
                        {
                            return departureTime;
                        }
                        else
                        {
                            if (previousStation==0)
                            {
                                previousStation = item.StationID;
                            }
                            else
                            {
                                AddToDepartureTime(ref departureTime, item, ref previousStation);
                            }
                        }
                    }
                }
            }

            return departureTime;
        }

        private static void AddToDepartureTime(ref TimeSpan departureTime, RoutesStation item, ref int previousStation)
        {
            int currentStation = item.StationID;
            TimeSpan stationTime = new TimeSpan(0, 5, 0);
            int timeBetweenStations = GetTravelTimeByStations(previousStation, currentStation).Time;
            TimeSpan travelTime = new TimeSpan(0, timeBetweenStations, 0);
            departureTime.Add(stationTime);
            departureTime.Add(travelTime);
            previousStation = currentStation;
        }

        public static List<TimeTable> CalculateTimetable(Route route, out List<Station> stations)
        {
            stations = new List<Station>();
            List<TimeSpan?>  arrivalTimes = new List<TimeSpan?>();
            List<TimeSpan?>  departureTimes = new List<TimeSpan?>();
            List<TimeTable> timetables = new List<TimeTable>();

            using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
            {
                GetRouteStations(route, stations, entity);

                CalculateTimes(route, stations, arrivalTimes, departureTimes);
            }

            for (int i = 0; i < stations.Count; i++)
            {
                TimeTable timeTable = new TimeTable() 
                { 
                    Station=stations[i],
                    ArrivalTime=arrivalTimes[i],
                    DepartureTime=departureTimes[i]
                };
                timetables.Add(timeTable);
            }
            return timetables;
        }

        private static void CalculateTimes(Route route, List<Station> stations, 
            List<TimeSpan?> arrivalTimes, List<TimeSpan?> departureTimes)
        {
            arrivalTimes.Add(null);
            departureTimes.Add(route.DepartureTime);
            int previousStation = stations[0].StationID;
            for (int i = 1; i < stations.Count; i++)
            {
                int timeBetweenStations = GetTravelTimeByStations(previousStation, stations[i].StationID).Time;
                TimeSpan travelTime = new TimeSpan(0, timeBetweenStations, 0);
                travelTime+=(TimeSpan)departureTimes[i - 1];
                arrivalTimes.Add(travelTime);
                TimeSpan? departureTime;
                if (i == stations.Count - 1)
                {
                    departureTime = null;
                }
                else
                {
                    TimeSpan arrivalTime = (TimeSpan)arrivalTimes[i];
                    arrivalTime+=new TimeSpan(0, 5, 0);
                    departureTime = arrivalTime;
                }
                departureTimes.Add(departureTime);
            }
        }

        private static void GetRouteStations(Route route, List<Station> stations, RailwayCompanyEntities entity)
        {
            var routesStations = entity.RoutesStations;
            foreach (var item in routesStations)
            {
                if (item.RouteID == route.RouteID)
                {
                    stations.Add(GetStationByID(item.StationID));
                }
            }
        }

        public static List<Station> GetRouteStations(string route)
        {
            var stationNames = Regex.Split(route, "->", RegexOptions.IgnorePatternWhitespace);
            List<Station> stations = new List<Station>();
            foreach (var name in stationNames)
            {
                using (RailwayCompanyEntities entity = new RailwayCompanyEntities())
                {
                    Station station = entity.Stations.FirstOrDefault(s => s.StationName == name);
                    if (station!=null)
                    {
                        stations.Add(station);
                    }
                }
            }
            return stations;
        }
    }
}
